/*
 * Copyright 2023, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 *
 * 龙芯3A6000 LS7A2000桥片 HPET定时器驱动
 */

#include "loongson_hpet.h"

#include <KernelExport.h>
#include <SupportDefs.h>
#include <arch/cpu.h>
#include <vm/vm.h>

#define TRACE_LOONGSON_HPET
#ifdef TRACE_LOONGSON_HPET
#define TRACE(x...) dprintf("loongson_hpet: " x)
#else
#define TRACE(x...)
#endif

#define ERROR(x...) dprintf("loongson_hpet: " x)

// 全局HPET实例
static loongson_hpet *sHPET = NULL;

// 寄存器读写函数
static inline uint32
read_hpet_reg32(addr_t base, uint32 reg)
{
    return *(volatile uint32 *)(base + reg);
}

static inline void
write_hpet_reg32(addr_t base, uint32 reg, uint32 val)
{
    *(volatile uint32 *)(base + reg) = val;
}

static inline uint64
read_hpet_reg64(addr_t base, uint32 reg)
{
    uint64 value;
    value = (uint64)read_hpet_reg32(base, reg + 4) << 32;
    value |= read_hpet_reg32(base, reg);
    return value;
}

static inline void
write_hpet_reg64(addr_t base, uint32 reg, uint64 val)
{
    write_hpet_reg32(base, reg, (uint32)val);
    write_hpet_reg32(base, reg + 4, (uint32)(val >> 32));
}

// HPET定时器初始化
status_t
loongson_hpet_init(void)
{
    TRACE("Initializing LS7A2000 HPET\n");

    // 分配HPET结构体
    sHPET = (loongson_hpet *)malloc(sizeof(loongson_hpet));
    if (sHPET == NULL)
    {
        ERROR("Failed to allocate loongson_hpet structure\n");
        return B_NO_MEMORY;
    }

    // 初始化结构体
    memset(sHPET, 0, sizeof(loongson_hpet));

    // 映射HPET寄存器空间
    sHPET->register_area = map_physical_memory("LS7A HPET registers",
                                               (phys_addr_t)LS7A_HPET_BASE_ADDRESS,
                                               LS7A_HPET_REGISTER_SIZE,
                                               B_ANY_KERNEL_ADDRESS,
                                               B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
                                               (void **)&sHPET->base_addr);

    if (sHPET->register_area < 0)
    {
        ERROR("Failed to map HPET registers\n");
        free(sHPET);
        sHPET = NULL;
        return sHPET->register_area;
    }

    // 读取HPET能力寄存器
    uint64 capabilities = read_hpet_reg64(sHPET->base_addr, LS7A_HPET_CAPABILITIES);

    // 解析能力寄存器
    sHPET->timer_count = LS7A_HPET_TIMER_COUNT;   // 龙芯HPET固定为4个定时器
    sHPET->clock_period = LS7A_HPET_CLOCK_PERIOD; // 龙芯HPET固定时钟周期
    sHPET->is_64bit = true;                       // 龙芯HPET支持64位模式
    sHPET->legacy_capable = true;                 // 龙芯HPET支持传统替换路由

    TRACE("HPET capabilities: %#" B_PRIx64 "\n", capabilities);
    TRACE("HPET timer count: %lu\n", sHPET->timer_count);
    TRACE("HPET clock period: %" B_PRIu64 " fs\n", sHPET->clock_period);

    // 禁用HPET
    loongson_hpet_set_enabled(false);

    // 清除所有中断状态
    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_INTERRUPT_STATUS, 0xFFFFFFFF);

    // 初始化所有定时器
    for (uint32 i = 0; i < sHPET->timer_count; i++)
    {
        // 禁用定时器中断
        uint32 config = read_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(i));
        config &= ~LS7A_HPET_TN_INT_ENB_CNF;
        write_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(i), config);
    }

    // 重置主计数器
    write_hpet_reg64(sHPET->base_addr, LS7A_HPET_MAIN_COUNTER, 0);

    // 启用HPET
    loongson_hpet_set_enabled(true);

    TRACE("LS7A2000 HPET initialized successfully\n");
    return B_OK;
}

// HPET定时器卸载
void loongson_hpet_uninit(void)
{
    if (sHPET != NULL)
    {
        // 禁用HPET
        loongson_hpet_set_enabled(false);

        // 释放映射区域
        if (sHPET->register_area >= 0)
        {
            delete_area(sHPET->register_area);
        }

        free(sHPET);
        sHPET = NULL;
    }
}

// 设置HPET启用状态
status_t
loongson_hpet_set_enabled(bool enabled)
{
    if (sHPET == NULL)
        return B_NO_INIT;

    uint32 config = read_hpet_reg32(sHPET->base_addr, LS7A_HPET_CONFIGURATION);

    if (enabled)
        config |= LS7A_HPET_CFG_ENABLE;
    else
        config &= ~LS7A_HPET_CFG_ENABLE;

    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_CONFIGURATION, config);

    TRACE("HPET %s\n", enabled ? "enabled" : "disabled");
    return B_OK;
}

// 设置HPET传统模式
status_t
loongson_hpet_set_legacy_mode(bool enabled)
{
    if (sHPET == NULL)
        return B_NO_INIT;

    if (!sHPET->legacy_capable)
        return B_NOT_SUPPORTED;

    uint32 config = read_hpet_reg32(sHPET->base_addr, LS7A_HPET_CONFIGURATION);

    if (enabled)
        config |= LS7A_HPET_CFG_LEGACY;
    else
        config &= ~LS7A_HPET_CFG_LEGACY;

    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_CONFIGURATION, config);

    TRACE("HPET legacy mode %s\n", enabled ? "enabled" : "disabled");
    return B_OK;
}

// 配置HPET定时器
status_t
loongson_hpet_configure_timer(uint32 timer_index, uint32 config)
{
    if (sHPET == NULL)
        return B_NO_INIT;

    if (timer_index >= sHPET->timer_count)
        return B_BAD_VALUE;

    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(timer_index), config);

    TRACE("Configured timer %lu with config %#lx\n", timer_index, config);
    return B_OK;
}

// 设置HPET定时器
status_t
loongson_hpet_set_timer(uint32 timer_index, uint64 relative_timeout, bool periodic)
{
    if (sHPET == NULL)
        return B_NO_INIT;

    if (timer_index >= sHPET->timer_count)
        return B_BAD_VALUE;

    // 计算超时值
    uint64 current = read_hpet_reg64(sHPET->base_addr, LS7A_HPET_MAIN_COUNTER);
    uint64 timeout_value = current + relative_timeout;

    // 设置比较器值
    write_hpet_reg64(sHPET->base_addr, LS7A_HPET_TIMER_COMPARATOR(timer_index), timeout_value);

    // 配置定时器
    uint32 timer_config = read_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(timer_index));

    // 设置周期性模式
    if (periodic)
        timer_config |= LS7A_HPET_TN_TYPE_CNF;
    else
        timer_config &= ~LS7A_HPET_TN_TYPE_CNF;

    // 启用中断
    timer_config |= LS7A_HPET_TN_INT_ENB_CNF;

    // 应用配置
    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(timer_index), timer_config);

    TRACE("Set timer %lu to %" B_PRIu64 " (current: %" B_PRIu64 ", timeout: %" B_PRIu64 ")\n",
          timer_index, relative_timeout, current, timeout_value);

    return B_OK;
}

// 清除HPET定时器
status_t
loongson_hpet_clear_timer(uint32 timer_index)
{
    if (sHPET == NULL)
        return B_NO_INIT;

    if (timer_index >= sHPET->timer_count)
        return B_BAD_VALUE;

    // 禁用定时器中断
    uint32 config = read_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(timer_index));
    config &= ~LS7A_HPET_TN_INT_ENB_CNF;
    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_TIMER_CONFIG(timer_index), config);

    // 清除中断状态
    write_hpet_reg32(sHPET->base_addr, LS7A_HPET_INTERRUPT_STATUS, 1 << timer_index);

    TRACE("Cleared timer %lu\n", timer_index);
    return B_OK;
}

// 获取HPET计数器值
uint64
loongson_hpet_get_counter(void)
{
    if (sHPET == NULL)
        return 0;

    return read_hpet_reg64(sHPET->base_addr, LS7A_HPET_MAIN_COUNTER);
}

// 获取HPET时钟周期
uint64
loongson_hpet_get_period(void)
{
    if (sHPET == NULL)
        return 0;

    return sHPET->clock_period;
}